package ex1.ga;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import ex1.utils.*;

public class GeneticAlgorithm {

	private List<Chromosome> _population;
	private int _totalFitness = Integer.MIN_VALUE;
	
	private int _elitism;
	private double _mutationProbability;
	private double _crossoverProbability;
	
	public GeneticAlgorithm(List<Chromosome> population, int chromosomeSize, int elitism,
			double crossoverProbability, double mutationProbability)
	{
		_elitism = elitism;
		_mutationProbability = mutationProbability;
		_crossoverProbability = crossoverProbability;
		
		_population = population;
	}
	
	public GeneticAlgorithm(int populationSize, int chromosomeSize, int elitism,
			double crossoverProbability, double mutationProbability)
	{
		this(null, chromosomeSize, elitism, crossoverProbability, mutationProbability);
		_population = new ArrayList<Chromosome>();
		
		for (int i = 0; i < populationSize; i++) {
			_population.add(Chromosome.CreateRandomChromosome(chromosomeSize));
		}
	}
	
	public void runOneRound(FitnessFunction fitnessFunc)
	{
		if(_totalFitness == Integer.MIN_VALUE)
		{
			this.sortByFitness(fitnessFunc);
		}
		
		List<Chromosome> nextGenPopulation = new ArrayList<Chromosome>();
		
		this.addElitists(nextGenPopulation);
		
		while(nextGenPopulation.size() < _population.size())
		{
			Pair<Chromosome, Chromosome> nextGen = this.addToNextGeneration();
			nextGenPopulation.add(nextGen.getFirst());
			nextGenPopulation.add(nextGen.getSecond());
		}
		
		_population = nextGenPopulation;
		this.sortByFitness(fitnessFunc);
		
		if (_population.get(_population.size()-1).getFitness() == Integer.MIN_VALUE)
		{
			@SuppressWarnings("unused")
			int temp = 1;
		}
		System.out.print(_population.get(_population.size()-1).toString() + ",");
		System.out.print(_population.get(_population.size()-1).getFitness()+ ",");
		int averageFitness = _totalFitness / _population.size();
		System.out.println(averageFitness);
		//System.out.print(", ");
	}

	public List<Chromosome> getPopulation()
	{
		return _population;
	}
	
	private void addElitists(List<Chromosome> nextGenPopulation) {
	
		for(int i = 0; i < this._elitism; i++)
		{
			nextGenPopulation.add(this._population.get(this._population.size() - i - 1));
		}
	}

	private Pair<Chromosome, Chromosome> addToNextGeneration() {
		
		Pair<Chromosome, Chromosome> parents = 
			new Pair<Chromosome, Chromosome>(this.rouletteSelection(), this.rouletteSelection());
		
		if(Utils.RandomGenerator().nextDouble() < this._crossoverProbability)
		{
			Pair<Chromosome, Chromosome> childs = parents.getFirst().Reproduce(parents.getSecond());
			
			childs.getFirst().Mutate(_mutationProbability);
			childs.getSecond().Mutate(_mutationProbability);
			
			return childs;
		}
		else
		{
			return parents;
		}
	}
	
	private void sortByFitness(FitnessFunction fitnessFunc) {
		ChromosomeComparator comparator = new ChromosomeComparator();
		
		this._totalFitness = 0;
		for (Chromosome chrom : _population) 
		{
			this._totalFitness += chrom.updateFitness(fitnessFunc);
		}
		
		Collections.sort(_population, comparator);
	}
	
	private Chromosome rouletteSelection() {
		
		calcTotalFitness();
		
		double threshold = Utils.RandomGenerator().nextDouble() * this._totalFitness;
		int aggregateFitness = 0;
		//population is sorted by fitness in ascending order
		for (Chromosome chromosome : _population)
		{
			aggregateFitness += chromosome.getFitness();
			if (aggregateFitness >= threshold)
			{
				return chromosome;
			}
		}
		
		System.out.println("Warning: Roulette Selection did not find a valid chromosome, total fitness = " + _totalFitness + " rand = " + threshold + " aggregateFitness = " + aggregateFitness);
				
		return null;
	}

	/**
	 * Calc the total fitness of the population
	 */
	private void calcTotalFitness() {
		_totalFitness = 0;
		for (Chromosome chromosome : _population)
		{
			_totalFitness += chromosome.getFitness();			
		}
	}
	

}
